feat: add Chinese i18n, tablet layout, 9Router integration, and log v…#101
feat: add Chinese i18n, tablet layout, 9Router integration, and log v…#101wojiadexiaoming-copy wants to merge 1 commit intomithun50:mainfrom
Conversation
📝 WalkthroughWalkthroughThis PR introduces comprehensive internationalization support (English/Chinese), a new 9Router CLI proxy management system with native Android service integration, tablet-responsive UI layouts, enhanced AI provider configuration with custom base URLs, and localized UI strings across all screens and widgets. Major new components include dedicated screens for 9Router terminal/webview access, a centralized localization system, responsive layout utilities, and log parsing with localization support. Changes
Sequence Diagram(s)sequenceDiagram
participant User as User
participant UI as Flutter UI
participant Bridge as NativeBridge
participant MainActivity as MainActivity
participant Service as NineRouterService
participant Notif as Notification System
User->>UI: Tap "Start 9Router"
UI->>Bridge: startNineRouterService()
Bridge->>MainActivity: invokeMethod('startNineRouterService')
MainActivity->>Service: NineRouterService.start(context)
Service->>Service: setIsRunning(true)
Service->>Service: createNotificationChannel()
Service->>Notif: startForeground(NOTIFICATION_ID, notification)
Notif-->>User: Display "9Router is running"
Service->>Service: acquirePartialWakeLock()
Service-->>MainActivity: return true
MainActivity-->>Bridge: return true
Bridge-->>UI: return true
UI->>UI: setState(isRunning: true)
User->>UI: Navigate to Console
UI->>UI: Load WebViewController
UI->>UI: Load http://127.0.0.1:20128/
Note over UI: WebView displays 9Router mgmt console
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
21 issues found across 66 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="flutter_app/lib/screens/provider_detail_screen.dart">
<violation number="1" location="flutter_app/lib/screens/provider_detail_screen.dart:54">
P2: ProviderDetailScreen initializes the base URL field from the provider default instead of the persisted config, so reopening can reset a saved custom baseUrl when saving.</violation>
<violation number="2" location="flutter_app/lib/screens/provider_detail_screen.dart:97">
P2: Custom provider Base URL can be saved as empty because _save() doesn’t validate _effectiveBaseUrl before persisting it.</violation>
</file>
<file name="flutter_app/lib/screens/setup_wizard_screen.dart">
<violation number="1" location="flutter_app/lib/screens/setup_wizard_screen.dart:101">
P2: Fallback error message is still hardcoded in English, so Chinese locale shows "Unknown error" when state.error is null.</violation>
<violation number="2" location="flutter_app/lib/screens/setup_wizard_screen.dart:153">
P3: Footer attribution still hardcodes the English literal "by" instead of using a localized string, leaving an untranslated UI string in an otherwise localized screen.</violation>
</file>
<file name="scripts/fetch-proot-windows.ps1">
<violation number="1" location="scripts/fetch-proot-windows.ps1:10">
P2: Hardcoding a local proxy for all downloads makes the script fail on machines/CI without that proxy running. Consider making the proxy optional (use existing env vars or a parameter) instead of forcing 127.0.0.1:7897.</violation>
<violation number="2" location="scripts/fetch-proot-windows.ps1:32">
P2: 7-Zip is hardcoded to a single install path, so the script exits even when 7-Zip is installed elsewhere (e.g., Program Files (x86) or on PATH). This makes the script unnecessarily brittle for other Windows setups.</violation>
</file>
<file name="flutter_app/lib/providers/node_provider.dart">
<violation number="1" location="flutter_app/lib/providers/node_provider.dart:66">
P2: Corrupted notification text in `NodeStatus.error` causes garbled user-facing message.</violation>
</file>
<file name="flutter_app/lib/l10n/app_strings.dart">
<violation number="1" location="flutter_app/lib/l10n/app_strings.dart:343">
P2: Custom provider base URL helper text still references CLIProxy port 18790, but the new 9Router/CLIProxy integration runs on port 20128; this guidance can mislead users into configuring a non-running endpoint.</violation>
</file>
<file name="flutter_app/lib/models/ai_provider.dart">
<violation number="1" location="flutter_app/lib/models/ai_provider.dart:29">
P3: User-facing description contains a corrupted replacement character ("�?") likely from encoding damage, causing broken UI text.</violation>
</file>
<file name="flutter_app/lib/screens/logs_screen.dart">
<violation number="1" location="flutter_app/lib/screens/logs_screen.dart:270">
P2: Screenshot snackbar text is hardcoded in Chinese, bypassing AppStrings localization and causing non-Chinese locales to show Chinese text.</violation>
</file>
<file name="整合方案.md">
<violation number="1" location="整合方案.md:21">
P2: The new integration document says the CLIProxy UI is served on port 18790 (/management.html), but the implementation uses 9Router on port 20128 for the management WebView. This conflicting guidance can lead to misconfiguration—please align the document with the actual ports/endpoints used in the app.</violation>
</file>
<file name="flutter_app/lib/screens/nine_router_terminal_screen.dart">
<violation number="1" location="flutter_app/lib/screens/nine_router_terminal_screen.dart:52">
P2: `_startPty()` can be triggered concurrently (init, refresh, retry) with no guard. Because it performs async work and reassigns `_pty`, overlapping executions can race and leave the UI attached to the wrong PTY or send the auto command to a newer/older instance.</violation>
<violation number="2" location="flutter_app/lib/screens/nine_router_terminal_screen.dart:119">
P2: _startPty awaits multiple async operations and then calls setState without checking mounted, which can cause `setState()` after dispose if the screen is left during startup.</violation>
<violation number="3" location="flutter_app/lib/screens/nine_router_terminal_screen.dart:169">
P3: Loading-state text is hardcoded in Chinese instead of using localized strings, so English users will still see Chinese during startup.</violation>
</file>
<file name="flutter_app/lib/screens/cliproxy_install_screen.dart">
<violation number="1" location="flutter_app/lib/screens/cliproxy_install_screen.dart:109">
P2: Non-zero install failures still mark the install as finished and return `true`, so the caller may proceed as if installation succeeded.</violation>
</file>
<file name="flutter_app/lib/utils/log_parser.dart">
<violation number="1" location="flutter_app/lib/utils/log_parser.dart:109">
P2: Severity tags like `[ERR]`/`[WARN]` are stripped in `_stripTime` before `_isError`/`_isWarn` run, so logs that rely on bracket tags can be misclassified as info/system.</violation>
<violation number="2" location="flutter_app/lib/utils/log_parser.dart:133">
P2: Substring success matching treats negative log messages like "Disconnected" as success because it matches "connected" (and similar for "not ready"). This can mislabel service health in the UI.</violation>
</file>
<file name="flutter_app/lib/screens/cliproxy_screen.dart">
<violation number="1" location="flutter_app/lib/screens/cliproxy_screen.dart:49">
P2: setState is called after async awaits in _stopCliProxy without checking mounted, which can throw if the screen is disposed before the awaits complete.</violation>
<violation number="2" location="flutter_app/lib/screens/cliproxy_screen.dart:330">
P2: `_isStarting` is never updated, so the loading/disabled UI path is unreachable and users can trigger repeated start flows concurrently.</violation>
<violation number="3" location="flutter_app/lib/screens/cliproxy_screen.dart:452">
P2: setState is called after awaited operations in _start without checking mounted, risking "setState() called after dispose()" if the user navigates away mid-startup.</violation>
</file>
<file name="flutter_app/lib/screens/terminal_screen.dart">
<violation number="1" location="flutter_app/lib/screens/terminal_screen.dart:255">
P2: Hardcoded, corrupted Chinese screenshot message bypasses AppStrings localization, causing garbled text and no locale switching for non-zh users.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| _apiKeyController = | ||
| TextEditingController(text: widget.existingApiKey ?? ''); | ||
| _customModelController = TextEditingController(); | ||
| _baseUrlController = TextEditingController(text: widget.provider.baseUrl); |
There was a problem hiding this comment.
P2: ProviderDetailScreen initializes the base URL field from the provider default instead of the persisted config, so reopening can reset a saved custom baseUrl when saving.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At flutter_app/lib/screens/provider_detail_screen.dart, line 54:
<comment>ProviderDetailScreen initializes the base URL field from the provider default instead of the persisted config, so reopening can reset a saved custom baseUrl when saving.</comment>
<file context>
@@ -25,29 +26,38 @@ class _ProviderDetailScreenState extends State<ProviderDetailScreen> {
+ _apiKeyController =
+ TextEditingController(text: widget.existingApiKey ?? '');
_customModelController = TextEditingController();
+ _baseUrlController = TextEditingController(text: widget.provider.baseUrl);
- final existing = widget.existingModel ?? widget.provider.defaultModels.first;
</file context>
| provider: widget.provider, | ||
| apiKey: apiKey, | ||
| model: model, | ||
| baseUrl: _effectiveBaseUrl, |
There was a problem hiding this comment.
P2: Custom provider Base URL can be saved as empty because _save() doesn’t validate _effectiveBaseUrl before persisting it.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At flutter_app/lib/screens/provider_detail_screen.dart, line 97:
<comment>Custom provider Base URL can be saved as empty because _save() doesn’t validate _effectiveBaseUrl before persisting it.</comment>
<file context>
@@ -83,17 +94,20 @@ class _ProviderDetailScreenState extends State<ProviderDetailScreen> {
provider: widget.provider,
apiKey: apiKey,
model: model,
+ baseUrl: _effectiveBaseUrl,
);
if (mounted) {
</file context>
| child: SingleChildScrollView( | ||
| child: Text( | ||
| AppStrings.translateError( | ||
| state.error ?? 'Unknown error'), |
There was a problem hiding this comment.
P2: Fallback error message is still hardcoded in English, so Chinese locale shows "Unknown error" when state.error is null.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At flutter_app/lib/screens/setup_wizard_screen.dart, line 101:
<comment>Fallback error message is still hardcoded in English, so Chinese locale shows "Unknown error" when state.error is null.</comment>
<file context>
@@ -46,114 +48,116 @@ class _SetupWizardScreenState extends State<SetupWizardScreen> {
+ child: SingleChildScrollView(
+ child: Text(
+ AppStrings.translateError(
+ state.error ?? 'Unknown error'),
+ style: TextStyle(
+ color:
</file context>
| New-Item -ItemType Directory -Force -Path $outDir | Out-Null | ||
| # .deb 是 ar 格式,用 7-Zip 或手动解析 | ||
| # 尝试用 7-Zip | ||
| $7z = "C:\Program Files\7-Zip\7z.exe" |
There was a problem hiding this comment.
P2: 7-Zip is hardcoded to a single install path, so the script exits even when 7-Zip is installed elsewhere (e.g., Program Files (x86) or on PATH). This makes the script unnecessarily brittle for other Windows setups.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/fetch-proot-windows.ps1, line 32:
<comment>7-Zip is hardcoded to a single install path, so the script exits even when 7-Zip is installed elsewhere (e.g., Program Files (x86) or on PATH). This makes the script unnecessarily brittle for other Windows setups.</comment>
<file context>
@@ -0,0 +1,98 @@
+ New-Item -ItemType Directory -Force -Path $outDir | Out-Null
+ # .deb 是 ar 格式,用 7-Zip 或手动解析
+ # 尝试用 7-Zip
+ $7z = "C:\Program Files\7-Zip\7z.exe"
+ if (Test-Path $7z) {
+ & $7z e $debPath -o"$outDir" "data.tar*" -y | Out-Null
</file context>
| @@ -0,0 +1,98 @@ | |||
| # 从 Termux 仓库下载 proot 和 libtalloc,解压放到 jniLibs | |||
There was a problem hiding this comment.
P2: Hardcoding a local proxy for all downloads makes the script fail on machines/CI without that proxy running. Consider making the proxy optional (use existing env vars or a parameter) instead of forcing 127.0.0.1:7897.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/fetch-proot-windows.ps1, line 10:
<comment>Hardcoding a local proxy for all downloads makes the script fail on machines/CI without that proxy running. Consider making the proxy optional (use existing env vars or a parameter) instead of forcing 127.0.0.1:7897.</comment>
<file context>
@@ -0,0 +1,98 @@
+$TMP_DIR = "$env:TEMP\proot-fetch-$(Get-Random)"
+New-Item -ItemType Directory -Force -Path $TMP_DIR | Out-Null
+
+$env:HTTPS_PROXY = "http://127.0.0.1:7897"
+$env:HTTP_PROXY = "http://127.0.0.1:7897"
+
</file context>
| }), | ||
| onWebResourceError: (error) { | ||
| if (error.isForMainFrame ?? true) { | ||
| setState(() { |
There was a problem hiding this comment.
P2: setState is called after async awaits in _stopCliProxy without checking mounted, which can throw if the screen is disposed before the awaits complete.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At flutter_app/lib/screens/cliproxy_screen.dart, line 49:
<comment>setState is called after async awaits in _stopCliProxy without checking mounted, which can throw if the screen is disposed before the awaits complete.</comment>
<file context>
@@ -0,0 +1,494 @@
+ }),
+ onWebResourceError: (error) {
+ if (error.isForMainFrame ?? true) {
+ setState(() {
+ _loading = false;
+ _hasError = true;
</file context>
| content: Text(path != null | ||
| ? 'Screenshot saved: ${path.split('/').last}' | ||
| : 'Failed to capture screenshot'), | ||
| ? '截图已保�? ${path.split('/').last}' |
There was a problem hiding this comment.
P2: Hardcoded, corrupted Chinese screenshot message bypasses AppStrings localization, causing garbled text and no locale switching for non-zh users.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At flutter_app/lib/screens/terminal_screen.dart, line 255:
<comment>Hardcoded, corrupted Chinese screenshot message bypasses AppStrings localization, causing garbled text and no locale switching for non-zh users.</comment>
<file context>
@@ -251,8 +252,8 @@ class _TerminalScreenState extends State<TerminalScreen> {
content: Text(path != null
- ? 'Screenshot saved: ${path.split('/').last}'
- : 'Failed to capture screenshot'),
+ ? '截图已保�? ${path.split('/').last}'
+ : '截图失败'),
),
</file context>
| Center( | ||
| child: Text( | ||
| 'Requires ~500MB of storage and an internet connection', | ||
| 'by ${AppConstants.authorName} | ${AppConstants.orgName}', |
There was a problem hiding this comment.
P3: Footer attribution still hardcodes the English literal "by" instead of using a localized string, leaving an untranslated UI string in an otherwise localized screen.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At flutter_app/lib/screens/setup_wizard_screen.dart, line 153:
<comment>Footer attribution still hardcodes the English literal "by" instead of using a localized string, leaving an untranslated UI string in an otherwise localized screen.</comment>
<file context>
@@ -46,114 +48,116 @@ class _SetupWizardScreenState extends State<SetupWizardScreen> {
Center(
child: Text(
- 'Requires ~500MB of storage and an internet connection',
+ 'by ${AppConstants.authorName} | ${AppConstants.orgName}',
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
</file context>
| id: 'anthropic', | ||
| name: 'Anthropic', | ||
| description: 'Claude models — advanced reasoning and coding', | ||
| description: 'Claude models �?advanced reasoning and coding', |
There was a problem hiding this comment.
P3: User-facing description contains a corrupted replacement character ("�?") likely from encoding damage, causing broken UI text.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At flutter_app/lib/models/ai_provider.dart, line 29:
<comment>User-facing description contains a corrupted replacement character ("�?") likely from encoding damage, causing broken UI text.</comment>
<file context>
@@ -26,7 +26,7 @@ class AiProvider {
id: 'anthropic',
name: 'Anthropic',
- description: 'Claude models — advanced reasoning and coding',
+ description: 'Claude models �?advanced reasoning and coding',
icon: Icons.psychology,
color: Color(0xFFD97706),
</file context>
| description: 'Claude models �?advanced reasoning and coding', | |
| description: 'Claude models — advanced reasoning and coding', |
| children: [ | ||
| CircularProgressIndicator(), | ||
| SizedBox(height: 16), | ||
| Text('正在启动 9Router...'), |
There was a problem hiding this comment.
P3: Loading-state text is hardcoded in Chinese instead of using localized strings, so English users will still see Chinese during startup.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At flutter_app/lib/screens/nine_router_terminal_screen.dart, line 169:
<comment>Loading-state text is hardcoded in Chinese instead of using localized strings, so English users will still see Chinese during startup.</comment>
<file context>
@@ -0,0 +1,221 @@
+ children: [
+ CircularProgressIndicator(),
+ SizedBox(height: 16),
+ Text('正在启动 9Router...'),
+ ],
+ ))
</file context>
There was a problem hiding this comment.
Actionable comments posted: 17
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
flutter_app/lib/screens/splash_screen.dart (1)
123-154:⚠️ Potential issue | 🟠 MajorRefresh
nodeOkafter reinstalling Node.
nodeOkis read once from the initial bootstrap status. If Node is missing and the reinstall in Lines 139-149 succeeds, the flag still staysfalse, so the!openclawOk && nodeOkbranch is skipped in the same pass. A device missing both Node and OpenClaw still falls back to full setup instead of finishing the repair.💡 Suggested change
- final nodeOk = status['nodeInstalled'] == true; + var nodeOk = status['nodeInstalled'] == true; final openclawOk = status['openclawInstalled'] == true; final bypassOk = status['bypassInstalled'] == true; @@ if (!nodeOk) { setState(() => _status = AppStrings.reinstallingNode); try { final arch = await NativeBridge.getArch(); final nodeTarUrl = AppConstants.getNodeTarballUrl(arch); final filesDir = await NativeBridge.getFilesDir(); final nodeTarPath = '$filesDir/tmp/nodejs.tar.xz'; final dio = Dio(); await dio.download(nodeTarUrl, nodeTarPath); await NativeBridge.extractNodeTarball(nodeTarPath); + nodeOk = true; } catch (_) {} }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/screens/splash_screen.dart` around lines 123 - 154, nodeOk is only read from NativeBridge.getBootstrapStatus once, so if the reinstall block in the try/catch (using NativeBridge.getArch, AppConstants.getNodeTarballUrl, NativeBridge.extractNodeTarball) succeeds nodeOk remains false and the subsequent openclaw branch (!openclawOk && nodeOk) is skipped; update nodeOk after a successful reinstall (either by setting nodeOk = true immediately after extractNodeTarball succeeds or by re-querying NativeBridge.getBootstrapStatus) so the openclaw reinstall branch can run in the same pass (adjust the try/catch around the reinstall block in splash_screen.dart and ensure the subsequent if that checks nodeOk uses the updated value).flutter_app/lib/widgets/node_controls.dart (1)
40-67:⚠️ Potential issue | 🟡 MinorIncomplete localization - hardcoded English strings remain.
While most UI strings have been updated to use
AppStrings.*, these strings are still hardcoded in English:
- Line 42:
'Connected to ${state.gatewayHost}:${state.gatewayPort}'- Line 54:
'Pairing code: 'For consistency with the i18n effort, these should also use localized strings.
🌐 Suggested localization approach
Add corresponding entries to
AppStrings(example pattern):// In app_strings.dart static String get connectedTo => isChinese ? '已连接到' : 'Connected to'; static String get pairingCode => isChinese ? '配对码:' : 'Pairing code: ';Then update node_controls.dart:
Text( - 'Connected to ${state.gatewayHost}:${state.gatewayPort}', + '${AppStrings.connectedTo} ${state.gatewayHost}:${state.gatewayPort}', style: theme.textTheme.bodyMedium?.copyWith(🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/widgets/node_controls.dart` around lines 40 - 67, Replace the hardcoded English strings with localized entries from AppStrings: use AppStrings.connectedTo combined with state.gatewayHost and state.gatewayPort to build the connection text (replace 'Connected to ${state.gatewayHost}:${state.gatewayPort}') and use AppStrings.pairingCode as the label before displaying state.pairingCode (replace 'Pairing code: '). Update the Text and SelectableText usages in node_controls.dart so they reference AppStrings.connectedTo and AppStrings.pairingCode, preserving formatting/monospace and bold styles for the host/port and pairing code, and ensure spacing/punctuation matches the localized strings.flutter_app/lib/models/ai_provider.dart (1)
26-39:⚠️ Potential issue | 🟡 MinorFix encoding artifact in description.
The
anthropicdescription on line 29 contains corrupted characters (�?advanced). This appears to be the same UTF-8 encoding issue affecting other files.📝 Proposed fix
- description: 'Claude models �?advanced reasoning and coding', + description: 'Claude models — advanced reasoning and coding',🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/models/ai_provider.dart` around lines 26 - 39, The description string for the static AiProvider constant anthropic contains a UTF-8 artifact ("�?advanced"); locate the anthropic constant in ai_provider.dart and replace the corrupted characters in the description field with the intended text (e.g., "Claude models — advanced reasoning and coding" or "Claude models: advanced reasoning and coding") so the description is plain ASCII/UTF-8 text without encoding artifacts.flutter_app/lib/screens/node_screen.dart (1)
30-42:⚠️ Potential issue | 🟠 MajorGuard
_loadSettings()against disposal.Line 36 calls
setState()after awaited work without checking if the widget is still mounted. If the user navigates away beforePreferencesService.init()finishes, Flutter will throw a "setState() called after dispose" error. Add amountedcheck before callingsetState().🔧 Suggested fix
Future<void> _loadSettings() async { final prefs = PreferencesService(); await prefs.init(); final host = prefs.nodeGatewayHost ?? '127.0.0.1'; final port = prefs.nodeGatewayPort ?? 18789; final token = prefs.nodeGatewayToken ?? ''; + if (!mounted) return; setState(() { _isLocal = host == '127.0.0.1' || host == 'localhost'; _hostController.text = _isLocal ? '' : host; _portController.text = _isLocal ? '' : '$port'; _tokenController.text = _isLocal ? '' : token; _loading = false; });🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/screens/node_screen.dart` around lines 30 - 42, The _loadSettings function calls PreferencesService().init() then invokes setState; guard against the widget being disposed by checking the widget's mounted property after the await and before calling setState. Specifically, after awaiting prefs.init() (in _loadSettings) verify if (!mounted) return; then proceed to call setState to update _isLocal, _hostController.text, _portController.text, _tokenController.text and _loading so you never call setState on a disposed widget.
🟡 Minor comments (10)
flutter_app/lib/services/package_service.dart-24-24 (1)
24-24:⚠️ Potential issue | 🟡 MinorFix corrupted character in API doc comment.
Line 24 contains a mojibake glyph (
�?) and should be restored to a readable separator (for example:package id -> installed boolean) to avoid confusing generated docs and maintainers.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/services/package_service.dart` at line 24, Replace the corrupted mojibake glyph in the API doc comment in package_service.dart (the comment above the package lookup function) with a clear separator such as "->" so the line reads "Returns a map of package id -> installed boolean"; update the doc comment text around the unique symbol package id and installed to ensure generated docs are readable and the comment contains no non-ASCII mojibake.flutter_app/lib/services/capabilities/flash_capability.dart-37-37 (1)
37-37:⚠️ Potential issue | 🟡 MinorFix malformed character in inline comment.
Line 37 contains a mojibake/encoding artifact (
�?) in the comment. Please replace it with a normal dash for readability and clean source text.Proposed fix
- // Controller is stale/errored �?dispose and recreate + // Controller is stale/errored - dispose and recreate🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/services/capabilities/flash_capability.dart` at line 37, Replace the malformed mojibake sequence "�?" in the inline comment on the controller handling line with a normal dash and reword to "Controller is stale/errored - dispose and recreate" so the comment in flash_capability.dart (near the controller handling in the FlashCapability implementation) uses a clean ASCII dash; update the comment in the same location where the controller variable/logic is referenced (e.g., around the controller dispose/recreate block) to ensure readability.flutter_app/lib/constants.dart-34-34 (1)
34-34:⚠️ Potential issue | 🟡 MinorFix corrupted character in comment text (encoding/mojibake).
Line 34 contains a garbled character sequence (
�?). Please replace it with a normal dash/space phrase to keep source text clean and readable.Suggested fix
- // Node.js binary tarball �?downloaded directly by Flutter, extracted by Java. + // Node.js binary tarball — downloaded directly by Flutter, extracted by Java.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/constants.dart` at line 34, The comment containing the garbled sequence "�?": "Node.js binary tarball �?downloaded directly by Flutter, extracted by Java." should be cleaned; replace the corrupted characters with a normal hyphen or em-dash and whitespace so the comment reads e.g. "Node.js binary tarball — downloaded directly by Flutter, extracted by Java." to remove the mojibake; update the comment in flutter_app/lib/constants.dart where that exact comment string appears.flutter_app/lib/screens/configure_screen.dart-1-1 (1)
1-1:⚠️ Potential issue | 🟡 MinorRemove the UTF-8 BOM at file start.
Line 1 starts with a BOM character. Please remove it to avoid hidden-character churn in diffs and tooling.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/screens/configure_screen.dart` at line 1, Remove the UTF-8 BOM from the top of the file so the first character is the 'i' of "import 'dart:convert';" (i.e., save the file as UTF-8 without BOM); open configure_screen.dart, delete the hidden BOM character before the import statement, and re-save the file in UTF-8 (no BOM) to prevent hidden-character diffs and tooling issues.flutter_app/lib/providers/node_provider.dart-66-66 (1)
66-66:⚠️ Potential issue | 🟡 MinorFix corrupted user-facing notification text.
Line 66 is shown to users and currently contains mojibake (
�?). Please replace it with a valid separator so the notification reads correctly.Proposed fix
- text = 'Node error �?retrying'; + text = 'Node error - retrying';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/providers/node_provider.dart` at line 66, The user-facing notification string assigned to variable text contains mojibake ("Node error �?retrying"); update the assignment in node_provider (the code that sets text) to use a valid separator, e.g. replace with "Node error — retrying" or "Node error: retrying" so the notification reads correctly.flutter_app/lib/screens/splash_screen.dart-51-51 (1)
51-51:⚠️ Potential issue | 🟡 MinorGuard these status updates with
mounted.These
setStatecalls run after awaited work. If the splash screen gets replaced or disposed during startup, this path can still hitsetState() called after dispose.💡 Suggested change
- setState(() => _status = AppStrings.checkingSetup); + if (!mounted) return; + setState(() => _status = AppStrings.checkingSetup); @@ if (!bypassOk) { + if (!mounted) return; setState(() => _status = AppStrings.repairingBypass); await NativeBridge.installBionicBypass(); } @@ if (!nodeOk) { + if (!mounted) return; setState(() => _status = AppStrings.reinstallingNode); try { @@ if (!openclawOk && nodeOk) { + if (!mounted) return; setState(() => _status = AppStrings.reinstallingOpenClaw); try {Also applies to: 134-154
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/screens/splash_screen.dart` at line 51, The setState calls that update _status after awaiting startup work must be guarded by the widget's mounted flag to avoid calling setState after dispose; locate the async startup logic in the SplashScreen (where _status is set) and wrap the updates so they only run when mounted (e.g., check mounted before performing setState), and apply the same mounted guard for the other block that updates _status in the later startup flow (the second async sequence around the existing setState usage).flutter_app/lib/l10n/app_strings.dart-341-344 (1)
341-344:⚠️ Potential issue | 🟡 MinorLocalize the
Base URLfield label too.The helper text switches to Chinese, but
baseUrlstays English, so this form still renders mixed-language UI in Chinese locale.💡 Suggested change
- static String get baseUrl => 'Base URL'; + static String get baseUrl => _isChinese ? '基础 URL' : 'Base URL';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/l10n/app_strings.dart` around lines 341 - 344, The label getter baseUrl is not localized while baseUrlHelper uses _isChinese; update baseUrl to return a Chinese string when _isChinese is true (mirroring baseUrlHelper's pattern) so both baseUrl and baseUrlHelper are consistent; locate the static getter baseUrl in app_strings.dart and change it to branch on _isChinese returning the appropriate Chinese label (e.g., '基础 URL' or the project's preferred translation) otherwise return 'Base URL'.flutter_app/android/settings.gradle-21-22 (1)
21-22:⚠️ Potential issue | 🟡 MinorVerify third-party plugin compatibility with Kotlin 2.1.0.
This includes a major Kotlin version upgrade (1.9.0 → 2.1.0) and AGP bump to 8.9.1. Both are compatible with Flutter 3.2, but some third-party plugins may require updates or workarounds to work with Kotlin 2.1.0. Check your dependency versions (flutter_blue_plus, usb_serial, camera, webview_flutter, etc.) to ensure they support Kotlin 2.x, or prepare fallback solutions if compatibility issues arise.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/android/settings.gradle` around lines 21 - 22, Kotlin was bumped to 2.1.0 and AGP to 8.9.1; audit each third-party plugin (e.g., flutter_blue_plus, usb_serial, camera, webview_flutter) in the Gradle/Kotlin build files and plugin pubspecs to confirm Kotlin 2.x compatibility, update plugin versions to releases that support Kotlin 2.x or AGP 8.9.1, and where no compatible release exists, add explicit Kotlin-version or JVM toolchain overrides (or pin a compatible Kotlin/AGP combo) as a fallback; update dependency versions in pubspec.yaml and any build.gradle(kts) plugin blocks and run a clean build to validate fixes.flutter_app/lib/services/provider_config_service.dart-129-129 (1)
129-129:⚠️ Potential issue | 🟡 MinorFix encoding artifact in comment.
The comment contains corrupted characters (
�?models). This appears to be a UTF-8 encoding issue where an em dash or similar character was not preserved correctly.📝 Proposed fix
- // Merge provider entry �?models must be objects with `id`, not bare strings (`#83`, `#88`). + // Merge provider entry - models must be objects with `id`, not bare strings (`#83`, `#88`).🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/services/provider_config_service.dart` at line 129, The file-level comment "Merge provider entry �?models must be objects with `id`, not bare strings" contains a corrupted character; update the comment in provider_config_service.dart (look for the line containing "Merge provider entry") to replace the garbled sequence with the intended punctuation (e.g., an em dash — or a plain hyphen) so it reads cleanly like "Merge provider entry — models must be objects with `id`, not bare strings" (or equivalent readable text) to fix the UTF-8 artifact.flutter_app/lib/l10n/app_en.arb-1-171 (1)
1-171:⚠️ Potential issue | 🟡 MinorAdd the remaining new UI strings to the localization catalog.
This catalog still omits part of the surface added in the same PR—e.g. the 9Router card labels, raw/friendly log mode labels, screenshot status text, Base URL text, and multiple Node labels. That’s why the changed screens still fall back to
AppStrings.isChinesebranches and raw literals, so zh/en coverage is still incomplete.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/l10n/app_en.arb` around lines 1 - 171, The en ARB is missing several new UI string keys used by the updated screens—add the missing localization entries (and corresponding zh translations) for the 9Router card labels, raw/friendly log mode labels, screenshot status texts, Base URL label, and any additional Node labels referenced in the UI; specifically search the codebase for keys/identifiers like the 9Router card properties (e.g. keys used where the router card is constructed), log mode toggles (e.g. references to "raw" or "friendly" log modes), screenshot status usages (e.g. "screenshotSaved"/"screenshotFailed" or similar), "baseUrl" display key, and any Node label keys (beyond enableNode/disableNode) and add matching entries to app_en.arb so the UI no longer falls back to AppStrings.isChinese branches. Ensure the added keys follow the existing naming style and include descriptive messages and placeholders where the UI expects them.
🧹 Nitpick comments (16)
flutter_app/lib/widgets/terminal_toolbar.dart (1)
57-57: Clean up mojibake in the inline comment.Line 57 contains an encoding artifact (
�?), which makes the comment harder to read. Please normalize it to plain UTF-8 text.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/widgets/terminal_toolbar.dart` at line 57, The inline comment in terminal_toolbar.dart that reads "// Ctrl+a-z �?bytes 1-26" contains an encoding artifact; update the comment in the TerminalToolbar widget (the inline comment near the Ctrl+a-z line) to plain UTF-8 text such as "// Ctrl+a-z (bytes 1-26)" or "// Ctrl+a-z — bytes 1–26" so the mojibake is removed and the comment is readable.flutter_app/lib/screens/packages_screen.dart (2)
85-85: Unnecessary removal ofconst.
CircularProgressIndicator()with no arguments can beconst. The removal appears unintentional since this widget doesn't depend on any runtime values.🧹 Proposed fix
- ? Center(child: CircularProgressIndicator()) + ? const Center(child: CircularProgressIndicator())🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/screens/packages_screen.dart` at line 85, The Center child instantiation removed the const—revert by making the widget tree use compile-time constants where possible: change the Center(child: CircularProgressIndicator()) expression to use const for the CircularProgressIndicator (and Center if it has only const children) so CircularProgressIndicator() becomes const CircularProgressIndicator() to avoid unnecessary rebuilds in PackagesScreen.
1-1: Remove UTF-8 BOM marker from file.The file starts with a UTF-8 BOM (
U+FEFF), which is unnecessary for UTF-8 encoded Dart files and can cause issues with some tools and linters. Consider removing it.🧹 Proposed fix
-import 'package:flutter/material.dart'; +import 'package:flutter/material.dart';🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/screens/packages_screen.dart` at line 1, The file begins with a UTF-8 BOM character (U+FEFF) before the top-level import, which can break linters and tools; open flutter_app/lib/screens/packages_screen.dart and remove the invisible BOM at the very start so the first character is the "import 'package:flutter/material.dart';" line, then save the file as UTF-8 without BOM to ensure tooling and the import statement work correctly.flutter_app/lib/widgets/progress_step.dart (1)
1-1: Remove UTF-8 BOM marker from file.Same issue as other files - the UTF-8 BOM is unnecessary for Dart files.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/widgets/progress_step.dart` at line 1, The file contains a UTF-8 BOM marker at the start (before the import 'package:flutter/material.dart';) which should be removed; open flutter_app/lib/widgets/progress_step.dart, delete the BOM character at the top of the file so the first character is the 'i' of "import", and re-save the file as UTF-8 without BOM to avoid parsing issues in Dart.flutter_app/lib/utils/log_parser.dart (4)
164-166: Hardcoded port number in error message.The port
18789is hardcoded in the error translation. If this port is configurable elsewhere in the app, consider extracting it to a constant or making the message generic.🧹 Proposed fix
if (msg.contains('port') || msg.contains('Port')) { - return '端口错误:18789 端口可能被占用'; + return '端口错误:端口可能被占用'; }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/utils/log_parser.dart` around lines 164 - 166, The error translation in log_parser.dart hardcodes the port "18789" inside the conditional that checks msg (the block with if (msg.contains('port') || msg.contains('Port'))), so extract that literal into a configurable constant or read the actual configured port and use it in the returned string (or make the message generic) instead of embedding "18789"; update the message construction to reference the new constant or config value (e.g., DEFAULT_PORT or AppConfig.serverPort) so the text reflects the real/changed port and remove the hardcoded '18789' literal.
221-222: Add curly braces for multi-line if body.The
ifstatement body spans multiple lines but lacks curly braces. This violates Dart style guidelines and reduces readability.🧹 Proposed fix
- if (msg.contains('health') || msg.contains('Health')) - return '正在检查服务健康状态...'; + if (msg.contains('health') || msg.contains('Health')) { + return '正在检查服务健康状态...'; + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/utils/log_parser.dart` around lines 221 - 222, The if statement checking msg.contains('health') || msg.contains('Health') has a multi-line body but no curly braces; update that conditional in log_parser.dart (the branch that returns '正在检查服务健康状态...') to use braces around the return so the block is explicit and follows Dart style (e.g., wrap the return statement in { ... } for the if body).
1-1: Remove UTF-8 BOM marker from file.Same issue as other files - the UTF-8 BOM is unnecessary for Dart files.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/utils/log_parser.dart` at line 1, This file contains a leading UTF-8 BOM before the import statement ("import 'package:flutter/material.dart';"); remove the BOM character at the very start of the file so the import line begins at column 1 (no invisible characters) and save the file without BOM encoding to resolve the issue.
113-145: Classification methods have overlapping keyword matches.The boolean checks use substring matching that can produce ambiguous results:
_isSuccessmatches"started"while_isSystemmatches"Starting"— a message like"Starting gateway"would match_isSystem(checked 4th) but the substring"started"doesn't appear.- However,
"Gateway started successfully"contains both"started"(success) and could semantically be system-related.The current check order (error → warn → success → system → info) handles most cases correctly, but consider documenting this priority or adding more specific patterns to avoid future ambiguity.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/utils/log_parser.dart` around lines 113 - 145, The classifier methods (_isError, _isWarn, _isSuccess, _isSystem) produce overlapping matches because they use substring checks; update them to use stricter, case-insensitive patterns (e.g., regex with word boundaries or anchors) and normalize the input (trim + toLowerCase) so tokens like "started" vs "starting" do not collide, and consolidate ambiguous phrases (e.g., "gateway started", "starting gateway") into explicit patterns or priority rules; also add a short comment above the decision sequence documenting the intended priority (error → warn → success → system → info) so future maintainers understand why certain checks run first.flutter_app/lib/widgets/node_controls.dart (1)
1-1: Remove UTF-8 BOM marker from file.Same issue as other files - the UTF-8 BOM is unnecessary for Dart files.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/widgets/node_controls.dart` at line 1, This file contains a UTF-8 BOM at the very start (before the import 'package:flutter/material.dart'; line); remove the BOM character so the file begins directly with the import statement, save the file as UTF-8 without BOM, and re-run formatting/analysis to ensure no stray invisible characters remain.整合方案.md (1)
1-32: Internal design document - consider standardizing format.This integration plan document provides useful context about the architecture and implementation approach. For long-term maintainability, consider:
- Adding a header with date/author
- Using consistent Markdown formatting (proper code blocks for the ASCII diagrams)
- Moving to a
docs/folder if this should be preservedOtherwise, if this is temporary working notes, it can be removed after the PR is merged.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@整合方案.md` around lines 1 - 32, Add a standardized header and consistent Markdown formatting to the integration plan: prepend a one-line header with author and date and convert the ASCII architecture diagram and bullet lists into proper Markdown (use fenced code blocks for the proot Ubuntu diagram and use Markdown headings/bullets for sections), ensure key terms like "CLIProxyAPI", "OpenClaw", "management.html", "proot Ubuntu" and the step list are properly formatted as code or headings for readability, and if this doc is meant to be preserved move the file into a docs/ folder (or delete if it is ephemeral) to match repo conventions.flutter_app/lib/screens/terminal_screen.dart (1)
111-111: Fix mojibake in inline comments.Both comments contain
�?artifacts; please normalize encoding to avoid future copy/paste confusion.Also applies to: 118-118
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/screens/terminal_screen.dart` at line 111, Replace the mojibake in the inline comments that currently read "Ctrl+a-z �?bytes 1-26" (and the similar comment at lines around 118) with a clean, encoded ASCII comment such as "Ctrl+a-z bytes 1-26" or "Ctrl+a-z (bytes 1-26)"; locate the comment text in terminal_screen.dart and update both occurrences so they contain only normal printable characters and no �? artifacts.flutter_app/lib/screens/settings_screen.dart (1)
96-96: AvoidtoUpperCase()on localized strings.Transforming translated text at runtime is locale-fragile and unnecessary for non-Latin scripts; prefer casing directly in the translation values if required by design.
Also applies to: 137-137, 163-163, 211-211, 238-238
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/screens/settings_screen.dart` at line 96, The code calls toUpperCase() on translated strings before passing them to _sectionHeader (e.g., AppStrings.general.toUpperCase()); remove runtime uppercasing and instead use the appropriate translated/uppercased string from the localization file (e.g., add uppercase variants or edit the translation for the section header key) and pass that value directly to _sectionHeader; update all similar occurrences where AppStrings.*.toUpperCase() is used so the casing is handled in the localization strings rather than via toUpperCase() at runtime.flutter_app/lib/screens/onboarding_screen.dart (1)
73-73: Clean up mojibake in comment text.The
�?artifact in the comment indicates an encoding/copy issue; please replace it with the intended character sequence for readability.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/screens/onboarding_screen.dart` at line 73, Replace the mojibake sequence "�?" in the inline comment "// codes �?the shell must know the actual column count to avoid wrapping." with a readable punctuation/phrase (e.g., an em dash or colon) so the comment reads clearly, for example: "// codes — the shell must know the actual column count to avoid wrapping." Locate this comment by searching for the exact snippet "the shell must know the actual column count to avoid wrapping" and update it in the corresponding file.flutter_app/lib/screens/nine_router_webview_screen.dart (1)
52-56: Consider using AppStrings constants instead of inline ternary expressions.Several strings use inline
AppStrings.isChinese ? '中文' : 'English'patterns (lines 52, 56, 85-100) instead of theAppStrings.*constants used elsewhere in the codebase. This creates inconsistency and makes translations harder to maintain.♻️ Suggested approach
Add these strings to
AppStrings:// In app_strings.dart static String get nineRouterConsole => isChinese ? '9Router 控制台' : '9Router Console'; static String get nineRouterNotRunning => isChinese ? '9Router 未运行' : '9Router is not running'; static String get startNineRouterFirst => isChinese ? '请先在"9Router 终端"中启动服务,然后返回此页面。' : 'Please start the service in "9Router Terminal" first.';Then use them in the screen:
title: Text(AppStrings.nineRouterConsole), // ... Text(AppStrings.nineRouterNotRunning, ...), Text(AppStrings.startNineRouterFirst, ...),Also applies to: 84-100
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/screens/nine_router_webview_screen.dart` around lines 52 - 56, Replace inline ternary string expressions in nine_router_webview_screen.dart with AppStrings getters: add new getters in AppStrings (e.g., nineRouterConsole, nineRouterNotRunning, startNineRouterFirst) that return isChinese ? '9Router 控制台' : '9Router Console', isChinese ? '9Router 未运行' : '9Router is not running', and the corresponding start message respectively, then update usages in NineRouterWebviewScreen (title Text, tooltip, and the Text widgets around the "not running" message and instruction) to reference AppStrings.nineRouterConsole, AppStrings.nineRouterNotRunning, and AppStrings.startNineRouterFirst instead of inline ternaries.flutter_app/lib/screens/providers_screen.dart (1)
182-195: Consider using an enum instead of comparing localized strings for styling logic.Comparing
status == AppStrings.activeto determine badge colors ties visual styling to localized string values. If translations change or new locales are added with different string values, this logic could break.A more robust approach would be to return a status enum from
_statusLabeland derive both the display text and color from it.♻️ Suggested refactor using an enum
enum ProviderStatus { none, configured, active } ProviderStatus _providerStatus(AiProvider provider) { final isConfigured = _providers.containsKey(provider.id); if (!isConfigured) return ProviderStatus.none; if (_activeModel != null) { final isActive = provider.defaultModels.any((m) => _activeModel!.contains(m)) || _activeModel!.contains(provider.id); if (isActive) return ProviderStatus.active; } return ProviderStatus.configured; } String _statusText(ProviderStatus status) => switch (status) { ProviderStatus.active => AppStrings.active, ProviderStatus.configured => AppStrings.configured, ProviderStatus.none => '', }; Color _statusColor(ProviderStatus status) => switch (status) { ProviderStatus.active => AppColors.statusGreen, ProviderStatus.configured => AppColors.statusAmber, ProviderStatus.none => Colors.transparent, };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/screens/providers_screen.dart` around lines 182 - 195, The UI currently compares localized strings in _statusLabel (status == AppStrings.active) to choose colors, which is brittle; introduce a ProviderStatus enum (e.g., none, configured, active), add a helper _providerStatus(AiProvider provider) that inspects _providers and _activeModel to return the enum, and replace string comparisons with two helpers _statusText(ProviderStatus) to return the localized label and _statusColor(ProviderStatus) to return the correct Color; then update the badge rendering in providers_screen.dart to call _providerStatus(provider) and use _statusText/_statusColor instead of comparing status strings.flutter_app/lib/screens/cliproxy_screen.dart (1)
80-109: Move these zh/en literals into the localization layer.These inline
AppStrings.isChinese ? ... : ...branches reintroduce per-widget translation logic in a PR that otherwise centralizes strings. Please add getters for them and keep the screen onAppStrings/AppLocalizationsonly.Also applies to: 138-138, 168-181, 376-376, 471-476
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@flutter_app/lib/screens/cliproxy_screen.dart` around lines 80 - 109, Inline ternary literals in cliproxy_screen.dart (e.g., the dialog title "Start 9Router"/"启动 9Router", the instruction strings "Run the following command..." / "请在终端中运行以下命令启动 9Router:" , the command text '9router --port 20128 &', and the helper text 'Select "Hide to Tray", then come back and tap Reconnect.' / '启动后选择 Hide to Tray,然后返回点重新连接。') must be moved into the localization layer: add appropriate getters/properties to AppStrings or AppLocalizations (e.g., start9RouterTitle, runTerminalCommandText, nineRouterCommand, hideToTrayHint) and replace all AppStrings.isChinese ? ... : ... occurrences in CliproxyScreen (and the other noted occurrences in this file) with those getters; ensure the SelectableText uses the localized nineRouterCommand and update any uses at the other locations referenced to use the new localization keys.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 98aa7b7a-a557-440c-8b36-538c4cce421e
⛔ Files ignored due to path filters (11)
flutter_app/android/app/jniLibs/arm64-v8a/libproot.sois excluded by!**/*.soflutter_app/android/app/jniLibs/arm64-v8a/libprootloader.sois excluded by!**/*.soflutter_app/android/app/jniLibs/arm64-v8a/libtalloc.sois excluded by!**/*.soflutter_app/android/app/jniLibs/armeabi-v7a/libproot.sois excluded by!**/*.soflutter_app/android/app/jniLibs/armeabi-v7a/libprootloader.sois excluded by!**/*.soflutter_app/android/app/jniLibs/armeabi-v7a/libtalloc.sois excluded by!**/*.soflutter_app/android/app/jniLibs/x86_64/libproot.sois excluded by!**/*.soflutter_app/android/app/jniLibs/x86_64/libprootloader.sois excluded by!**/*.soflutter_app/android/app/jniLibs/x86_64/libtalloc.sois excluded by!**/*.soflutter_app/android/gradle/wrapper/gradle-wrapper.jaris excluded by!**/*.jarflutter_app/pubspec.lockis excluded by!**/*.lock
📒 Files selected for processing (55)
flutter_app/.flutter-plugins-dependenciesflutter_app/android/app/build.gradleflutter_app/android/app/src/main/AndroidManifest.xmlflutter_app/android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.javaflutter_app/android/app/src/main/kotlin/com/nxg/openclawproot/MainActivity.ktflutter_app/android/app/src/main/kotlin/com/nxg/openclawproot/NineRouterService.ktflutter_app/android/gradle/wrapper/gradle-wrapper.propertiesflutter_app/android/gradlewflutter_app/android/gradlew.batflutter_app/android/settings.gradleflutter_app/lib/app.dartflutter_app/lib/constants.dartflutter_app/lib/l10n/app_en.arbflutter_app/lib/l10n/app_localizations.dartflutter_app/lib/l10n/app_localizations_en.dartflutter_app/lib/l10n/app_localizations_zh.dartflutter_app/lib/l10n/app_strings.dartflutter_app/lib/l10n/app_zh.arbflutter_app/lib/models/ai_provider.dartflutter_app/lib/providers/node_provider.dartflutter_app/lib/screens/cliproxy_install_screen.dartflutter_app/lib/screens/cliproxy_screen.dartflutter_app/lib/screens/configure_screen.dartflutter_app/lib/screens/dashboard_screen.dartflutter_app/lib/screens/logs_screen.dartflutter_app/lib/screens/nine_router_terminal_screen.dartflutter_app/lib/screens/nine_router_webview_screen.dartflutter_app/lib/screens/node_screen.dartflutter_app/lib/screens/onboarding_screen.dartflutter_app/lib/screens/package_install_screen.dartflutter_app/lib/screens/packages_screen.dartflutter_app/lib/screens/provider_detail_screen.dartflutter_app/lib/screens/providers_screen.dartflutter_app/lib/screens/settings_screen.dartflutter_app/lib/screens/setup_wizard_screen.dartflutter_app/lib/screens/splash_screen.dartflutter_app/lib/screens/ssh_screen.dartflutter_app/lib/screens/terminal_screen.dartflutter_app/lib/services/bootstrap_service.dartflutter_app/lib/services/capabilities/flash_capability.dartflutter_app/lib/services/gateway_service.dartflutter_app/lib/services/native_bridge.dartflutter_app/lib/services/node_service.dartflutter_app/lib/services/package_service.dartflutter_app/lib/services/provider_config_service.dartflutter_app/lib/services/terminal_service.dartflutter_app/lib/utils/log_parser.dartflutter_app/lib/utils/responsive.dartflutter_app/lib/widgets/gateway_controls.dartflutter_app/lib/widgets/node_controls.dartflutter_app/lib/widgets/progress_step.dartflutter_app/lib/widgets/terminal_toolbar.dartscripts/fetch-proot-windows.ps1整合方案.md终端日志.md
| @@ -0,0 +1 @@ | |||
| {"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"camera_avfoundation","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\camera_avfoundation-0.9.23+2\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_blue_plus_darwin","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\flutter_blue_plus_darwin-7.0.3\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_pty","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\flutter_pty-0.4.2\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"geolocator_apple","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\geolocator_apple-2.3.13\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_foundation-2.6.0\\\\","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"permission_handler_apple","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\permission_handler_apple-9.4.7\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\shared_preferences_foundation-2.5.6\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_ios","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\url_launcher_ios-6.4.1\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"webview_flutter_wkwebview","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\webview_flutter_wkwebview-3.24.2\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"android":[{"name":"camera_android_camerax","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\camera_android_camerax-0.6.30\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_blue_plus_android","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\flutter_blue_plus_android-7.0.4\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_plugin_android_lifecycle","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\flutter_plugin_android_lifecycle-2.0.34\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_pty","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\flutter_pty-0.4.2\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"geolocator_android","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\geolocator_android-4.6.2\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_android","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_android-2.2.23\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"permission_handler_android","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\permission_handler_android-12.1.0\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_android","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\shared_preferences_android-2.4.23\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_android","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\url_launcher_android-6.3.29\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"usb_serial","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\usb_serial-0.5.2\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"webview_flutter_android","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\webview_flutter_android-4.10.15\\\\","native_build":true,"dependencies":[],"dev_dependency":false}],"macos":[{"name":"flutter_blue_plus_darwin","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\flutter_blue_plus_darwin-7.0.3\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"flutter_pty","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\flutter_pty-0.4.2\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"geolocator_apple","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\geolocator_apple-2.3.13\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_foundation","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_foundation-2.6.0\\\\","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_foundation","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\shared_preferences_foundation-2.5.6\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false},{"name":"url_launcher_macos","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\url_launcher_macos-3.2.5\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"webview_flutter_wkwebview","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\webview_flutter_wkwebview-3.24.2\\\\","shared_darwin_source":true,"native_build":true,"dependencies":[],"dev_dependency":false}],"linux":[{"name":"flutter_blue_plus_linux","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\flutter_blue_plus_linux-7.0.3\\\\","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"flutter_pty","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\flutter_pty-0.4.2\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_linux","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_linux-2.2.1\\\\","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_linux","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\shared_preferences_linux-2.4.1\\\\","native_build":false,"dependencies":["path_provider_linux"],"dev_dependency":false},{"name":"url_launcher_linux","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\url_launcher_linux-3.2.2\\\\","native_build":true,"dependencies":[],"dev_dependency":false}],"windows":[{"name":"flutter_pty","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\flutter_pty-0.4.2\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"geolocator_windows","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\geolocator_windows-0.2.5\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"path_provider_windows","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\path_provider_windows-2.3.0\\\\","native_build":false,"dependencies":[],"dev_dependency":false},{"name":"permission_handler_windows","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\permission_handler_windows-0.2.1\\\\","native_build":true,"dependencies":[],"dev_dependency":false},{"name":"shared_preferences_windows","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\shared_preferences_windows-2.4.1\\\\","native_build":false,"dependencies":["path_provider_windows"],"dev_dependency":false},{"name":"url_launcher_windows","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\url_launcher_windows-3.1.5\\\\","native_build":true,"dependencies":[],"dev_dependency":false}],"web":[{"name":"camera_web","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\camera_web-0.3.5+3\\\\","dependencies":[],"dev_dependency":false},{"name":"flutter_blue_plus_web","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\flutter_blue_plus_web-7.0.2\\\\","dependencies":[],"dev_dependency":false},{"name":"geolocator_web","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\geolocator_web-4.1.3\\\\","dependencies":[],"dev_dependency":false},{"name":"permission_handler_html","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\permission_handler_html-0.1.3+5\\\\","dependencies":[],"dev_dependency":false},{"name":"shared_preferences_web","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\shared_preferences_web-2.4.3\\\\","dependencies":[],"dev_dependency":false},{"name":"url_launcher_web","path":"C:\\\\Users\\\\YouTube\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\hosted\\\\pub.dev\\\\url_launcher_web-2.4.2\\\\","dependencies":[],"dev_dependency":false}]},"dependencyGraph":[{"name":"camera","dependencies":["camera_android_camerax","camera_avfoundation","camera_web","flutter_plugin_android_lifecycle"]},{"name":"camera_android_camerax","dependencies":[]},{"name":"camera_avfoundation","dependencies":[]},{"name":"camera_web","dependencies":[]},{"name":"flutter_blue_plus","dependencies":["flutter_blue_plus_android","flutter_blue_plus_darwin","flutter_blue_plus_linux","flutter_blue_plus_web"]},{"name":"flutter_blue_plus_android","dependencies":[]},{"name":"flutter_blue_plus_darwin","dependencies":[]},{"name":"flutter_blue_plus_linux","dependencies":[]},{"name":"flutter_blue_plus_web","dependencies":[]},{"name":"flutter_plugin_android_lifecycle","dependencies":[]},{"name":"flutter_pty","dependencies":[]},{"name":"geolocator","dependencies":["geolocator_android","geolocator_apple","geolocator_web","geolocator_windows"]},{"name":"geolocator_android","dependencies":[]},{"name":"geolocator_apple","dependencies":[]},{"name":"geolocator_web","dependencies":[]},{"name":"geolocator_windows","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"permission_handler","dependencies":["permission_handler_android","permission_handler_apple","permission_handler_html","permission_handler_windows"]},{"name":"permission_handler_android","dependencies":[]},{"name":"permission_handler_apple","dependencies":[]},{"name":"permission_handler_html","dependencies":[]},{"name":"permission_handler_windows","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]},{"name":"url_launcher","dependencies":["url_launcher_android","url_launcher_ios","url_launcher_linux","url_launcher_macos","url_launcher_web","url_launcher_windows"]},{"name":"url_launcher_android","dependencies":[]},{"name":"url_launcher_ios","dependencies":[]},{"name":"url_launcher_linux","dependencies":[]},{"name":"url_launcher_macos","dependencies":[]},{"name":"url_launcher_web","dependencies":[]},{"name":"url_launcher_windows","dependencies":[]},{"name":"usb_serial","dependencies":[]},{"name":"webview_flutter","dependencies":["webview_flutter_android","webview_flutter_wkwebview"]},{"name":"webview_flutter_android","dependencies":[]},{"name":"webview_flutter_wkwebview","dependencies":[]}],"date_created":"2026-03-31 11:11:33.051186","version":"3.41.6","swift_package_manager_enabled":{"ios":false,"macos":false}} No newline at end of file | |||
There was a problem hiding this comment.
Remove this generated manifest from source control.
It is machine-generated, contains absolute Pub cache paths from a local Windows profile, and even says it should not be checked into version control. Keeping it in the repo leaks local-machine metadata and guarantees noisy diffs on every dependency refresh.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@flutter_app/.flutter-plugins-dependencies` at line 1, Remove the generated
.flutter-plugins-dependencies file from source control and stop committing it:
delete the file from the repo (git rm --cached or git rm and commit the
deletion), add ".flutter-plugins-dependencies" to .gitignore (or add a rule that
ignores generated Flutter plugin manifests), and ensure any branch/PRs do not
re-add the file; reference the file name .flutter-plugins-dependencies in your
changes and include a commit message like "Remove generated Flutter plugin
manifest from VCS and add to .gitignore".
| const val NOTIFICATION_ID = 7 | ||
| var isRunning = false | ||
| private set |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "NineRouterService.kt" -o -name "nine_router_terminal_screen.dart" | head -20Repository: mithun50/openclaw-termux
Length of output: 211
🏁 Script executed:
cat -n flutter_app/android/app/src/main/kotlin/com/nxg/openclawproot/NineRouterService.ktRepository: mithun50/openclaw-termux
Length of output: 4416
🏁 Script executed:
cat -n flutter_app/lib/screens/nine_router_terminal_screen.dartRepository: mithun50/openclaw-termux
Length of output: 8592
Service state does not indicate proxy health—9router is launched independently.
This service only manages its own Android lifecycle; start()/stop() control the keep-alive service, not the 9router daemon. The daemon itself launches separately via shell command in nine_router_terminal_screen.dart (line 40), so isRunning = true only confirms the service is running, not that port 20128 is actually responsive. If the 9router process crashes, isRunning remains true, creating a false positive.
Additionally, the 24-hour wakelock at line 65 (acquire(24 * 60 * 60 * 1000L)) is a critical battery drain issue for a keep-alive service.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@flutter_app/android/app/src/main/kotlin/com/nxg/openclawproot/NineRouterService.kt`
around lines 16 - 18, The service currently sets isRunning (var isRunning) true
for its Android lifecycle but doesn't verify the 9router daemon health; change
the start/stop logic in NineRouterService (the NOTIFICATION_ID block and where
isRunning is set) to perform an active health check against the 9router listen
port (20128) — e.g., attempt a TCP connect or HTTP probe to localhost:20128
after launching and periodically while service runs, and only set isRunning =
true when the probe succeeds (set false and notify/attempt restart on failure).
Also remove the hardcoded 24-hour wakelock acquire(24 * 60 * 60 * 1000L);
instead acquire a short timeout wakelock or use periodic scheduled checks
(AlarmManager/WorkManager) and release the wakelock promptly to avoid long
battery drain. Ensure all changes reference the existing isRunning variable,
notification code (NOTIFICATION_ID), and the acquire(...) wakelock call so
behavior is updated consistently.
| private fun acquireWakeLock() { | ||
| releaseWakeLock() | ||
| val pm = getSystemService(Context.POWER_SERVICE) as PowerManager | ||
| wakeLock = pm.newWakeLock( | ||
| PowerManager.PARTIAL_WAKE_LOCK, | ||
| "OpenClaw::NineRouterWakeLock" | ||
| ) | ||
| wakeLock?.acquire(24 * 60 * 60 * 1000L) | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find and read the NineRouterService.kt file to understand the context
find . -name "NineRouterService.kt" -type fRepository: mithun50/openclaw-termux
Length of output: 152
🏁 Script executed:
# Get file size and explore the service structure
wc -l flutter_app/android/app/src/main/kotlin/com/nxg/openclawproot/NineRouterService.ktRepository: mithun50/openclaw-termux
Length of output: 154
🏁 Script executed:
# Read the entire file to understand service purpose and lifecycle
cat -n flutter_app/android/app/src/main/kotlin/com/nxg/openclawproot/NineRouterService.ktRepository: mithun50/openclaw-termux
Length of output: 4416
🌐 Web query:
PARTIAL_WAKE_LOCK Android CPU awake battery drain
💡 Result:
PARTIAL_WAKE_LOCK in Android keeps the CPU running even when the screen is off, preventing the device from entering low-power states and causing significant battery drain if held excessively or stuck. Key points from official Android documentation: - Partial wake locks are acquired via PowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, tag).acquire. They must be released promptly with release to avoid drain. - Excessive use (total >2 hours in 24h period, excluding audio) or stuck locks (>1 hour in background) are flagged by Android Vitals in Play Console, potentially impacting app visibility. - Use only when necessary; prefer alternatives like WorkManager, JobScheduler, or foreground services which manage wake locks automatically. - Common issues: leaks (not releasing), misuse in background, or from libraries/APIs like AlarmManager (alarm), FCM (GCM_MESSAGE). - Best practices: Use timeouts, proper naming with package/class, reference counting, avoid PII in tags. - Recent enforcement (2025-2026): Metrics out of beta, affecting app quality ratings. To debug: Use adb shell dumpsys power, Android Vitals dashboard.
Citations:
- 1: https://developer.android.com/topic/performance/vitals/excessive-wakelock
- 2: https://developer.android.com/develop/background-work/background-tasks/awake/wakelock/identify-wls
- 3: http://developer.android.google.cn/topic/performance/vitals/stuck-wakelock
- 4: https://developer.android.com/reference/android/os/PowerManager.WakeLock
- 5: https://developer.android.com/develop/background-work/background-tasks/awake/wakelock/set
- 6: https://developer.android.com/reference/android/os/PowerManager
- 7: https://developer.android.com/develop/background-work/background-tasks/awake/wakelock
- 8: https://developer.android.com/develop/background-work/background-tasks/awake
🌐 Web query:
foreground service wakelock necessary network server Android
💡 Result:
No, a foreground service does not automatically acquire or hold a wake lock to keep the CPU continuously awake. A foreground service primarily increases your app's process priority to reduce the chance of it being killed by the system and requires a visible notification, but it does not prevent the CPU from entering low-power (Doze/suspend) states when your code is idle or waiting, such as during network operations to a server. For network/server communication (e.g., dataSync foreground service type for uploads/downloads), the CPU runs in short bursts: network hardware/kernel wakes it briefly when data arrives via interrupts, processes it, then idles again. The Linux kernel manages this aggressively for battery efficiency. A partial wake lock (PowerManager.PARTIAL_WAKE_LOCK) is necessary if your app requires the CPU to remain continuously active (e.g., tight polling loops, constant processing without idle waits) while the screen is off, to avoid suspension/delays. Official docs recommend using wake locks only as a last resort in foreground services when suspension would harm UX, and to release them quickly. Network I/O alone does not qualify—use alternatives like WorkManager if possible. Declare WAKE_LOCK permission, acquire via PowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "tag"). Best practices: acquire/release promptly, use timeouts, tag meaningfully, show notification. For Android 14+, use dataSync type for network transfers: declare FOREGROUND_SERVICE_DATA_SYNC permission and android:foregroundServiceType="dataSync".
Citations:
- 1: https://developer.android.com/develop/background-work/background-tasks/awake/wakelock/best-practices
- 2: https://developer.android.com/develop/background-work/background-tasks/awake
- 3: https://developer.android.com/develop/background-work/services/fgs/service-types
- 4: https://medium.com/@oshin.agrawal00/foreground-service-does-not-mean-your-cpu-is-awake-05c5fcb387d2
- 5: https://developer.android.com/develop/background-work/background-tasks/awake/wakelock/set
24-hour partial wakelock is excessively long and will cause severe battery drain.
A PARTIAL_WAKE_LOCK kept for 24 hours (line 65) prevents the CPU from entering low-power states, which triggers Android Vitals warnings for excessive wakelock use (flagged when exceeding ~2 hours in a 24-hour period). For a foreground service running a network server, the wakelock is likely unnecessary—foreground services already prevent process termination, and the CPU can idle efficiently while the kernel interrupts it when network data arrives. If a wakelock is truly required, the duration must be drastically reduced with documented justification.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@flutter_app/android/app/src/main/kotlin/com/nxg/openclawproot/NineRouterService.kt`
around lines 58 - 66, The acquireWakeLock() function currently acquires a
PARTIAL_WAKE_LOCK for 24 hours (wakeLock?.acquire(24 * 60 * 60 * 1000L)), which
will cause severe battery drain and Android Vitals warnings; either remove the
wakelock entirely (foreground services typically don't need one) or replace the
24-hour timeout with a short, documented timeout (e.g., a few minutes) and only
acquire it when absolutely necessary, ensuring you release it promptly via
releaseWakeLock(); update any related logic using wakeLock and
PowerManager.PARTIAL_WAKE_LOCK to reflect the new behavior and add a brief
comment explaining the justification for the chosen timeout if you keep one.
| #!/usr/bin/env bash | ||
|
|
||
| ############################################################################## | ||
| ## | ||
| ## Gradle start up script for UN*X | ||
| ## | ||
| ############################################################################## | ||
|
|
||
| # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. | ||
| DEFAULT_JVM_OPTS="" | ||
|
|
||
| APP_NAME="Gradle" | ||
| APP_BASE_NAME=`basename "$0"` | ||
|
|
||
| # Use the maximum available, or set MAX_FD != -1 to use that value. | ||
| MAX_FD="maximum" | ||
|
|
||
| warn ( ) { | ||
| echo "$*" | ||
| } | ||
|
|
||
| die ( ) { | ||
| echo | ||
| echo "$*" | ||
| echo | ||
| exit 1 | ||
| } | ||
|
|
||
| # OS specific support (must be 'true' or 'false'). | ||
| cygwin=false | ||
| msys=false | ||
| darwin=false | ||
| case "`uname`" in | ||
| CYGWIN* ) | ||
| cygwin=true | ||
| ;; | ||
| Darwin* ) | ||
| darwin=true | ||
| ;; | ||
| MINGW* ) | ||
| msys=true | ||
| ;; | ||
| esac | ||
|
|
||
| # Attempt to set APP_HOME | ||
| # Resolve links: $0 may be a link | ||
| PRG="$0" | ||
| # Need this for relative symlinks. | ||
| while [ -h "$PRG" ] ; do | ||
| ls=`ls -ld "$PRG"` | ||
| link=`expr "$ls" : '.*-> \(.*\)$'` | ||
| if expr "$link" : '/.*' > /dev/null; then | ||
| PRG="$link" | ||
| else | ||
| PRG=`dirname "$PRG"`"/$link" | ||
| fi | ||
| done | ||
| SAVED="`pwd`" | ||
| cd "`dirname \"$PRG\"`/" >/dev/null | ||
| APP_HOME="`pwd -P`" | ||
| cd "$SAVED" >/dev/null | ||
|
|
||
| CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar | ||
|
|
||
| # Determine the Java command to use to start the JVM. | ||
| if [ -n "$JAVA_HOME" ] ; then | ||
| if [ -x "$JAVA_HOME/jre/sh/java" ] ; then | ||
| # IBM's JDK on AIX uses strange locations for the executables | ||
| JAVACMD="$JAVA_HOME/jre/sh/java" | ||
| else | ||
| JAVACMD="$JAVA_HOME/bin/java" | ||
| fi | ||
| if [ ! -x "$JAVACMD" ] ; then | ||
| die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME | ||
|
|
||
| Please set the JAVA_HOME variable in your environment to match the | ||
| location of your Java installation." | ||
| fi | ||
| else | ||
| JAVACMD="java" | ||
| which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. | ||
|
|
||
| Please set the JAVA_HOME variable in your environment to match the | ||
| location of your Java installation." | ||
| fi | ||
|
|
||
| # Increase the maximum file descriptors if we can. | ||
| if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then | ||
| MAX_FD_LIMIT=`ulimit -H -n` | ||
| if [ $? -eq 0 ] ; then | ||
| if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then | ||
| MAX_FD="$MAX_FD_LIMIT" | ||
| fi | ||
| ulimit -n $MAX_FD | ||
| if [ $? -ne 0 ] ; then | ||
| warn "Could not set maximum file descriptor limit: $MAX_FD" | ||
| fi | ||
| else | ||
| warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" | ||
| fi | ||
| fi | ||
|
|
||
| # For Darwin, add options to specify how the application appears in the dock | ||
| if $darwin; then | ||
| GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" | ||
| fi | ||
|
|
||
| # For Cygwin, switch paths to Windows format before running java | ||
| if $cygwin ; then | ||
| APP_HOME=`cygpath --path --mixed "$APP_HOME"` | ||
| CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` | ||
| JAVACMD=`cygpath --unix "$JAVACMD"` | ||
|
|
||
| # We build the pattern for arguments to be converted via cygpath | ||
| ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` | ||
| SEP="" | ||
| for dir in $ROOTDIRSRAW ; do | ||
| ROOTDIRS="$ROOTDIRS$SEP$dir" | ||
| SEP="|" | ||
| done | ||
| OURCYGPATTERN="(^($ROOTDIRS))" | ||
| # Add a user-defined pattern to the cygpath arguments | ||
| if [ "$GRADLE_CYGPATTERN" != "" ] ; then | ||
| OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" | ||
| fi | ||
| # Now convert the arguments - kludge to limit ourselves to /bin/sh | ||
| i=0 | ||
| for arg in "$@" ; do | ||
| CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` | ||
| CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option | ||
|
|
||
| if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition | ||
| eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` | ||
| else | ||
| eval `echo args$i`="\"$arg\"" | ||
| fi | ||
| i=$((i+1)) | ||
| done | ||
| case $i in | ||
| (0) set -- ;; | ||
| (1) set -- "$args0" ;; | ||
| (2) set -- "$args0" "$args1" ;; | ||
| (3) set -- "$args0" "$args1" "$args2" ;; | ||
| (4) set -- "$args0" "$args1" "$args2" "$args3" ;; | ||
| (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; | ||
| (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; | ||
| (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; | ||
| (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; | ||
| (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; | ||
| esac | ||
| fi | ||
|
|
||
| # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules | ||
| function splitJvmOpts() { | ||
| JVM_OPTS=("$@") | ||
| } | ||
| eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS | ||
| JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" | ||
|
|
||
| exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check gradle-wrapper.properties for the configured version
cat flutter_app/android/gradle/wrapper/gradle-wrapper.properties 2>/dev/null || echo "Properties file not found"
# Check if gradle-wrapper.jar exists
ls -la flutter_app/android/gradle/wrapper/gradle-wrapper.jar 2>/dev/null || echo "Wrapper JAR not found"Repository: mithun50/openclaw-termux
Length of output: 374
🏁 Script executed:
# Read the actual gradlew script from the repository
head -160 flutter_app/android/gradlew | cat -nRepository: mithun50/openclaw-termux
Length of output: 6158
🌐 Web query:
Gradle 8.11.1 gradlew wrapper script official format
💡 Result:
The official gradlew wrapper script for Gradle 8.11.1 is a POSIX-compliant shell script generated by the Gradle Wrapper task. It resolves the project directory (APP_HOME), sets up the classpath to gradle/wrapper/gradle-wrapper.jar, determines the Java executable, applies default JVM options like '-Dfile.encoding=UTF-8 "-Xmx64m" "-Xms64m"', and executes the wrapper JAR with arguments passed through, using xargs for proper quoting of JVM opts from DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS. The script includes handling for symlinks, Cygwin/MSYS path conversion, specific OS tweaks (AIX, Darwin, etc.), and requires a POSIX shell. To generate it for Gradle 8.11.1, run ./gradlew wrapper --gradle-version 8.11.1 (assuming an existing wrapper) or gradle wrapper --gradle-version 8.11.1 with Gradle installed. This updates gradle/wrapper/gradle-wrapper.properties to distributionUrl=https://services.gradle.org/distributions/gradle-8.11.1-bin.zip (or -all.zip). The script format has remained stable across recent versions (e.g., 8.1 to current master), with minor updates like CDPATH handling. The full script can be viewed in the Gradle GitHub repo at https://raw.githubusercontent.com/gradle/gradle/master/gradlew (current master, compatible with 8.11.1) or generated fresh for exact match.
Citations:
- 1: https://docs.gradle.org/8.11/userguide/gradle_wrapper.html
- 2: https://docs.gradle.org/current/userguide/gradle_wrapper.html
- 3: https://docs.gradle.org/8.11/userguide/installation.html
- 4: https://docs.gradle.org/8.11.1/release-notes.html
- 5: https://docs.gradle.org/8.11/dsl/org.gradle.api.tasks.wrapper.Wrapper.html
- 6: https://raw.githubusercontent.com/gradle/gradle/master/gradlew
- 7: https://github.com/gradle/gradle
- 8: https://docs.gradle.org/current/dsl/org.gradle.api.tasks.wrapper.Wrapper.html
🏁 Script executed:
#!/bin/bash
# Fetch the official Gradle gradlew script from GitHub
curl -s "https://raw.githubusercontent.com/gradle/gradle/master/gradlew" | head -160 | cat -n
echo "------- COMPARISON -------"
echo "Checking for backticks in official script:"
curl -s "https://raw.githubusercontent.com/gradle/gradle/master/gradlew" | grep -c '`' | head -1
echo "Checking for 'function' keyword in official script:"
curl -s "https://raw.githubusercontent.com/gradle/gradle/master/gradlew" | grep -c '^function ' | head -1Repository: mithun50/openclaw-termux
Length of output: 6736
Regenerate the Gradle wrapper script for Gradle 8.11.1.
The gradlew script in the repository does not match the official Gradle 8.11.1 wrapper format. While gradle-wrapper.properties correctly specifies version 8.11.1, the script itself is from an older Gradle version. The official Gradle 8.11.1 wrapper uses #!/bin/sh (POSIX), $() command substitution, modern syntax patterns, and includes Apache License headers—none of which are present in the current script. This version mismatch between the properties file and the actual script can cause compatibility issues.
Run ./gradlew wrapper --gradle-version=8.11.1 from the flutter_app/android directory to regenerate the correct wrapper script for this version.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@flutter_app/android/gradlew` around lines 1 - 160, The gradlew script is an
older, non-POSIX wrapper and must be regenerated to match Gradle 8.11.1; run the
Gradle wrapper command in the flutter_app/android directory (./gradlew wrapper
--gradle-version=8.11.1) to replace the current script with the official
POSIX-compliant 8.11.1 version (which updates the shebang, uses $()
substitution, modern syntax and license header) so the CLASSPATH pointing at
gradle/wrapper/gradle-wrapper.jar and runtime behavior (symbols like
APP_BASE_NAME, APP_HOME, splitJvmOpts, JVM_OPTS and exec of
org.gradle.wrapper.GradleWrapperMain) are consistent with
gradle-wrapper.properties.
| ), | ||
| ), | ||
| cardTheme: CardTheme( | ||
| cardTheme: CardThemeData( |
There was a problem hiding this comment.
🧩 Analysis chain
🌐 Web query:
What Flutter version introduced CardThemeDataandDialogThemeData, and in that version what are the accepted types for ThemeData.cardThemeandThemeData.dialogTheme?
💡 Result:
Flutter version 3.27.0-0.0.pre introduced both CardThemeData and DialogThemeData as part of the component theme normalization effort. In that version, the accepted types for ThemeData.cardTheme and ThemeData.dialogTheme were changed to Object? to accept both the legacy *Theme classes (e.g., CardTheme, DialogTheme) and the new *ThemeData classes during the transition period. Later versions finalized these to CardThemeData? and DialogThemeData?.
Citations:
- 1: https://docs.flutter.dev/release/breaking-changes/component-theme-normalization
- 2: https://docs.flutter.dev/release/breaking-changes/material-theme-system-updates
- 3: Normalize
ThemeData.cardThemeflutter/flutter#153254 - 4: https://docs.flutter.dev/release/breaking-changes/component-theme-normalization-updates
🏁 Script executed:
# Find Flutter version specification
git ls-files | grep -E "(pubspec\.yaml|\.flutter-version|\.fvm/fvm_config\.json)" | head -20Repository: mithun50/openclaw-termux
Length of output: 92
🏁 Script executed:
# Search for pubspec.yaml files
fd pubspec.yamlRepository: mithun50/openclaw-termux
Length of output: 92
🏁 Script executed:
# Check the main app.dart file and surrounding context
fd app.dart | head -5Repository: mithun50/openclaw-termux
Length of output: 92
🏁 Script executed:
# Read pubspec.yaml to find Flutter version
cat flutter_app/pubspec.yaml | head -50Repository: mithun50/openclaw-termux
Length of output: 1062
🏁 Script executed:
# Read app.dart around the mentioned lines to see the actual code changes
cat -n flutter_app/lib/app.dart | sed -n '90,110p; 160,180p; 220,240p; 290,310p'Repository: mithun50/openclaw-termux
Length of output: 3737
Update Flutter SDK version constraint or revert to legacy theme classes.
The code uses CardThemeData and DialogThemeData (lines 98, 168, 229, 299), which were introduced in Flutter 3.27.0-0.0.pre. However, the project's pubspec.yaml allows Flutter versions as low as 3.2.0, creating a breaking compatibility issue. Either update the SDK constraint to >=3.27.0, or revert these to the legacy CardTheme and DialogTheme classes.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@flutter_app/lib/app.dart` at line 98, The project uses the newer Flutter
theme classes CardThemeData and DialogThemeData (references: CardThemeData,
DialogThemeData) which require Flutter >=3.27.0; either bump the SDK constraint
in pubspec.yaml to ">=3.27.0" so these classes are supported, or revert all
occurrences of CardThemeData and DialogThemeData back to the legacy CardTheme
and DialogTheme usages (replace each CardThemeData and DialogThemeData
instantiation and imports accordingly) to maintain compatibility with the
current >=3.2.0 constraint.
| String get _effectiveBaseUrl => _isCustomProvider | ||
| ? _baseUrlController.text.trim() | ||
| : widget.provider.baseUrl; |
There was a problem hiding this comment.
Validate the custom Base URL before saving.
_save() validates the API key and model, but a custom provider can still submit '' or a malformed URL from _baseUrlController. That either degrades into a generic save failure or persists a broken endpoint.
🔧 Suggested fix
final model = _effectiveModel;
if (model.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(AppStrings.modelEmpty)),
);
return;
}
+ final baseUrl = _effectiveBaseUrl;
+ if (_isCustomProvider) {
+ final parsedBaseUrl = Uri.tryParse(baseUrl);
+ if (baseUrl.isEmpty ||
+ parsedBaseUrl == null ||
+ !parsedBaseUrl.hasScheme ||
+ parsedBaseUrl.host.isEmpty) {
+ // Show a localized validation error here.
+ return;
+ }
+ }
setState(() => _saving = true);
try {
await ProviderConfigService.saveProviderConfig(
provider: widget.provider,
apiKey: apiKey,
model: model,
- baseUrl: _effectiveBaseUrl,
+ baseUrl: baseUrl,
);Also applies to: 75-98, 221-228
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@flutter_app/lib/screens/provider_detail_screen.dart` around lines 44 - 46,
The _effectiveBaseUrl getter and _save() flow allow empty or malformed custom
base URLs from _baseUrlController to be saved; add explicit validation in
_save() (when _isCustomProvider is true) to trim _baseUrlController.text, ensure
it is non-empty and a well-formed HTTP/HTTPS URI (e.g., Uri.parse(...) with
hasScheme and host or a RegExp), surface a user-facing error (same way API
key/model validation reports) and abort saving if invalid; update any other save
paths that use _effectiveBaseUrl (referenced around lines 75-98 and 221-228) to
rely on the validated value rather than persisting the raw controller text.
| title: const Text('Node.js'), | ||
| subtitle: Text(_status['nodeInstalled'] == true |
There was a problem hiding this comment.
Localize remaining hardcoded product/status labels in Settings.
These changed literals (Node.js, OpenClaw, Go (Golang), Homebrew, OpenSSH, and AI Gateway for Android) will remain English under zh locale, causing mixed-language UI.
Also applies to: 189-190, 196-197, 201-202, 206-207, 240-243
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@flutter_app/lib/screens/settings_screen.dart` around lines 182 - 183, The
listed product/status labels (e.g., Text('Node.js'), Text('OpenClaw'), Text('Go
(Golang)'), Text('Homebrew'), Text('OpenSSH'), Text('AI Gateway for Android'))
in flutter_app/lib/screens/settings_screen.dart are hardcoded and must be
localized; replace those literal Text strings and any status strings derived
from _status (e.g., in ListTile title/subtitle usages) with calls to your
localization API (for example AppLocalizations.of(context).<key>() or
S.of(context).<key>()) using new localization keys for each product and status
label, and update the ARB/intl resources accordingly so the zh locale gets
translations for those keys.
| label: '打开', | ||
| onPressed: () { |
There was a problem hiding this comment.
Replace hardcoded Chinese UI strings with localized keys.
These changed lines bypass AppStrings ('打开', '截图已保�?', '截图失败'), which breaks EN/zh runtime switching and introduces corrupted user text.
🌐 Suggested direction
- label: '打开',
+ label: AppStrings.openUrl,
...
- content: Text(path != null
- ? '截图已保�? ${path.split('/').last}'
- : '截图失败'),
+ content: Text(path != null
+ ? /* localized key with filename placeholder */
+ '${AppStrings.screenshot}: ${path.split('/').last}'
+ : AppStrings.error),
...
- child: const Text('打开'),
+ child: Text(AppStrings.openUrl),Also applies to: 255-256, 323-324
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@flutter_app/lib/screens/terminal_screen.dart` around lines 202 - 203, Replace
the hardcoded Chinese UI strings ('打开', '截图已保存', '截图失败') with the localized keys
from your AppStrings so runtime language switching works; locate the occurrences
around the `label: '打开'` property and the two other occurrences (near lines
255-256 and 323-324) in TerminalScreen (widgets building the buttons/snackbars)
and swap the literal texts for AppStrings.open, AppStrings.screenshotSaved, and
AppStrings.screenshotFailed (or the exact key names used in your app), updating
any Snackbar/Toast messages to use localization lookup (e.g.,
AppStrings.of(context).open) so the UI reads from the i18n resources instead of
hardcoded literals.
| $SCRIPT_DIR = Split-Path -Parent $MyInvocation.MyCommand.Path | ||
| $JNILIBS_DIR = "$SCRIPT_DIR\..\flutter_app\android\app\jniLibs" | ||
| $TMP_DIR = "$env:TEMP\proot-fetch-$(Get-Random)" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "Gradle files mentioning sourceSets/jniLibs:"
fd -a 'build\.gradle(\.kts)?$' flutter_app/android | while read -r f; do
echo "--- $f ---"
rg -n -C2 'sourceSets|jniLibs' "$f" || true
done
echo
echo "Existing jniLibs directories under flutter_app/android/app:"
fd -a -td 'jniLibs' flutter_app/android/app || trueRepository: mithun50/openclaw-termux
Length of output: 453
🏁 Script executed:
# Read the full build.gradle to see the complete sourceSets and packagingOptions configuration
cat -n /home/jailuser/git/flutter_app/android/app/build.gradle | head -100Repository: mithun50/openclaw-termux
Length of output: 3250
🏁 Script executed:
# Search for sourceSets block in the gradle file
rg -A 10 'sourceSets\s*{' /home/jailuser/git/flutter_app/android/app/build.gradleRepository: mithun50/openclaw-termux
Length of output: 159
🏁 Script executed:
# Search in parent gradle file and other config files
rg -n 'sourceSets' /home/jailuser/git/flutter_app/android/Repository: mithun50/openclaw-termux
Length of output: 50
🏁 Script executed:
# Check directory structure under app
find /home/jailuser/git/flutter_app/android/app -type d -name "src" -o -type d -name "jniLibs" | sortRepository: mithun50/openclaw-termux
Length of output: 165
🏁 Script executed:
# Check the structure under app/src
find /home/jailuser/git/flutter_app/android/app/src -type d | head -20Repository: mithun50/openclaw-termux
Length of output: 1361
🏁 Script executed:
# Check if there's content in the jniLibs directories
ls -la /home/jailuser/git/flutter_app/android/app/jniLibs 2>/dev/null || echo "No jniLibs at app level"
ls -la /home/jailuser/git/flutter_app/android/app/src/main/jniLibs 2>/dev/null || echo "No jniLibs at src/main level"Repository: mithun50/openclaw-termux
Length of output: 378
Fix native library source directory configuration.
The script writes to flutter_app/android/app/jniLibs, but Android Gradle Plugin sources native libraries from src/main/jniLibs by default. Without a sourceSets configuration explicitly mapping jniLibs.srcDirs, these native libraries will not be packaged into the APK.
Either:
- Update the script to write to
src/main/jniLibsinstead, or - Add
sourceSets { main { jniLibs.srcDirs = ["jniLibs"] } }to the app's build.gradle
(The packagingOptions.jniLibs.useLegacyPackaging = true setting only affects APK packaging format, not source directory location.)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/fetch-proot-windows.ps1` around lines 5 - 7, The script currently
sets JNILIBS_DIR to "$SCRIPT_DIR\..\flutter_app\android\app\jniLibs", which will
place native libraries outside of Gradle's default source directory and they
won't be packaged; update the script to set JNILIBS_DIR to the Gradle default
"$SCRIPT_DIR\..\flutter_app\android\app\src\main\jniLibs" (adjusting $SCRIPT_DIR
reference as needed), or alternatively leave the script as-is but add a
sourceSets block in the app module build.gradle that sets jniLibs.srcDirs =
["jniLibs"]; modify the JNILIBS_DIR variable or the app build.gradle so native
libs end up in Gradle's expected source directory (note: packagingOptions
settings do not change the source dir).
| $env:HTTPS_PROXY = "http://127.0.0.1:7897" | ||
| $env:HTTP_PROXY = "http://127.0.0.1:7897" | ||
|
|
||
| function Get-TermuxPkgUrl($arch, $pkgName) { | ||
| $indexUrl = "$TERMUX_REPO/dists/stable/main/binary-$arch/Packages" | ||
| $index = (Invoke-WebRequest -Uri $indexUrl -UseBasicParsing -Proxy "http://127.0.0.1:7897").Content |
There was a problem hiding this comment.
Don't force a localhost proxy for every download.
The script overwrites HTTP_PROXY/HTTPS_PROXY with 127.0.0.1:7897 and then hardcodes the same proxy on each Invoke-WebRequest. On any machine without that exact proxy, every fetch fails before extraction even starts. Make proxy usage opt-in instead of the default.
Also applies to: 62-70
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@scripts/fetch-proot-windows.ps1` around lines 10 - 15, The script currently
forces a localhost proxy by unconditionally setting $env:HTTP_PROXY and
$env:HTTPS_PROXY and by always passing -Proxy "http://127.0.0.1:7897" to
Invoke-WebRequest (e.g., inside Get-TermuxPkgUrl), causing failures on machines
without that proxy; change it to be opt-in by removing the unconditional env
assignments and instead read an optional proxy variable (e.g., $ProxyUrl or
existing env vars) and only add the -Proxy parameter to Invoke-WebRequest calls
(including those referenced around lines 62-70) when that variable is non-empty;
update Get-TermuxPkgUrl and all other Invoke-WebRequest sites to conditionally
use the proxy so normal hosts work without modification.
Summary
This PR adds several major improvements to the OpenClaw Android app:
🌐 Chinese / English Localization
AppStringsruntime getter pattern📱 Tablet Responsive Layout
Responsive.constrain()for max-width centering🔀 9Router Integration (Free AI Proxy)
NineRouterService) keeps the Node.js proxy alive independently from OpenClaw's terminalhttp://127.0.0.1:20128/for the 9Router web dashboard📊 Friendly Log Viewer
LogParserutility translates raw gateway logs into human-readable Chinese/English messages🔧 Custom Provider Support
http://127.0.0.1:20128/v1)📦 Other
NineRouterService.kt: new Android foreground service for 9Router process keepaliveNativeBridge: addedstartNineRouterService,stopNineRouterService,isNineRouterServiceRunningAndroidManifest.xml: registeredNineRouterServiceTesting
npm install -g 9router, runs via terminal, dashboard accessible at port 20128Summary by cubic
Adds Chinese/English localization, a tablet-friendly layout, 9Router (CLIProxy) integration with a dedicated service, and a friendlier, color‑coded log viewer. Also adds a configurable provider base URL and updates Android/Gradle tooling.
New Features
AppStrings).NineRouterService, a dedicated Terminal screen, and a WebView console forhttp://127.0.0.1:20128/; two new Dashboard cards link to both.LogParsertranslates raw logs to readable messages, color‑codes by level, toggles friendly/raw, and supports tap‑to‑copy raw entries.http://127.0.0.1:20128/v1), saved viaProviderConfigService.startNineRouterService,stopNineRouterService,isNineRouterServiceRunning.Dependencies
NineRouterServiceregistered in AndroidManifest.flutter_localizationsandintlresources (ARB and generated files).proot,prootloader, andtalloctojniLibsforarm64-v8a,armeabi-v7a, andx86_64.Written for commit 8afb220. Summary will update on new commits.
Summary by CodeRabbit
New Features
Chores